package org.vacoor.xqq.core.util;

import org.vacoor.nothing.common.codec.Hex;
import org.vacoor.nothing.common.crypto.digest.Hash;
import org.vacoor.nothing.common.util.Bytes;

import javax.script.*;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.Random;
import java.util.Stack;

/**
 * User: vacoor
 */
public class QQUtil {

    /**
     * 计算登陆指纹
     *
     * @param password
     * @param hexUin
     * @param verify
     * @return
     */
    public static String encrypt(char[] password, String hexUin, String verify) {
        return encrypt(password, decodeHexUin(hexUin), verify);
    }

    /**
     * 计算登陆指纹
     * 当直接使用 QQ 账号登陆时可以使用
     *
     * @param password
     * @param uin
     * @param verify
     * @return
     */
    public static String encrypt(char[] password, long uin, String verify) {
        // return encrypt(Bytes.toBytes(password), Bytes.toBytes(uin), verify);
        return encrypt(String.valueOf(password), uin, verify);
    }

    /**
     * update 20150903
     * 没时间处理了, 暂时直接调用 js
     */
    protected static String encrypt(String password, long uin, String verify) {
        /*
        verify = verify == null ? "" : verify;
        byte[] h1 = new Hash.MD5(passwdBytes).getBytes();
        byte[] s2 = new Hash.MD5(concat(passwdBytes, uinBytes)).getBytes();
        */
        try {
            ScriptEngine js = new ScriptEngineManager().getEngineByName("javascript");
            js.eval(new InputStreamReader(QQUtil.class.getResourceAsStream("/raw/mq_comm_frg.js")));
//            String encryption = (String) ((Invocable) js).invokeFunction("getEncryption", password, uin, verify);
//            js.eval(new InputStreamReader(QQUtil.class.getResourceAsStream("/raw/mq_comm.js")));
            String encryption = (String) ((Invocable) js).invokeFunction("getEncryption", password, uin, verify);
            return encryption;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ScriptException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 计算登陆指纹(QQ 登录密码加密)
     * 1. 对明文密码MD5 得到字节数组A
     * 2. 将 A 连接上check时得到的 QQ号码 字节数组 得到字节数组B
     * 3. 对 B MD5 得到字节数组 C
     * 4. 将 C 转换为HEX, 连接上验证码字符串得到 D
     * 5. 对 D MD5得到字节数组 E
     * 6. 将 E 转换为 HEX
     *
     * @param passwdBytes 明文密码字节数组
     * @param uinBytes    QQ 号码字节数组
     * @param verify      验证码
     * @return
     */
    protected static String encrypt(byte[] passwdBytes, byte[] uinBytes, String verify) {
        verify = verify == null ? "" : verify;
        byte[] linkedBytes = concat(new Hash.MD5(passwdBytes).getBytes(), uinBytes);
        return new Hash.MD5(new Hash.MD5(linkedBytes).toHex() + verify.toUpperCase()).toHex();
    }

    public static byte[] concat(byte[]... arrays) {
        int length = 0;
        for (byte[] array : arrays) {
            length += array.length;
        }
        byte[] result = new byte[length];
        int pos = 0;
        for (byte[] array : arrays) {
            System.arraycopy(array, 0, result, pos, array.length);
            pos += array.length;
        }
        return result;
    }

    /**
     * 解码 check 时获取的16进制 QQ 号, \x00\x00\x00\x00\x0d\x31\x45\x72
     */
    public static Long decodeHexUin(String hexUin) {
        return Bytes.toLong(Hex.decode(hexUin.replace("\\x", "")));
    }

    /**
     * 获取好友列表时HASH值的算法
     * from eqq.all.js#function P(b, i)
     * 腾讯这帮人， 没事总是在这个加密上秀算法
     * <p/>
     * update: 2013/12/20
     * <p/>
     */
    public static String hash(long uin, String ptwebqq) {
        /*
        int one[] = new int[4];
        for (int i = 0; i < ptwebqq.length(); i++) {
            one[i & 3] ^= ptwebqq.charAt(i); //one[i % 4] ^= ptwebqq.charAt(i);
        }

//        String[] salt = {"EC", "OK"};
        int[] two = new int[4];
        two[0] = (int) (uin >> 24 & 0xFF ^ 'E');
        two[1] = (int) (uin >> 16 & 0xFF ^ 'C');
        two[2] = (int) (uin >> 8 & 0xFF ^ 'O');
        two[3] = (int) (uin & 0xFF ^ 'K');

        byte[] hashed = new byte[8];
        for (int i = 0; i < hashed.length; i++) {
            hashed[i] = (byte) ((i & 1) == 0 ? one[i >> 1] : two[i >> 1]);
        }
        return Hex.encode(hashed);
        */
        int[] N = new int[4];
        for (int T = 0; T < ptwebqq.length(); T++) {
            N[T % 4] ^= ptwebqq.charAt(T);
        }
        String[] U = {"EC", "OK"};
        long[] V = new long[4];
        V[0] = uin >> 24 & 255 ^ U[0].charAt(0);
        V[1] = uin >> 16 & 255 ^ U[0].charAt(1);
        V[2] = uin >> 8 & 255 ^ U[1].charAt(0);
        V[3] = uin & 255 ^ U[1].charAt(1);

        long[] U1 = new long[8];

        for (int T = 0; T < 8; T++) {
            U1[T] = T % 2 == 0 ? N[T >> 1] : V[T >> 1];
        }

        String[] N1 = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
        String V1 = "";
        for (long aU1 : U1) {
            V1 += N1[(int) ((aU1 >> 4) & 15)];
            V1 += N1[(int) (aU1 & 15)];
        }
        return V1;
    }

    /**
     * @param uin     channel 时得到的uin, 可能和QQ号相同, 如果邮箱登录,则chanel会返回相应uin
     * @param ptwebqq
     * @return
     * @deprecated 201312 过时
     * <p/>
     * 获取好友列表时HASH值的算法, 其实就是排序中进行运算 from eqq.all.js
     */
    @Deprecated
    public static String _hash(long uin, String ptwebqq) {
        Stack<IdxRange> stack = new Stack<IdxRange>();
        byte[] bytes = new byte[4];
        bytes[0] = (byte) (uin >> 24 & 0xFF);
        bytes[1] = (byte) (uin >> 16 & 0xFF);
        bytes[2] = (byte) (uin >> 8 & 0xFF);
        bytes[3] = (byte) (uin & 0xFF);

        // 本来应该是对 ASCII 排序, Java中char/int 通用, 就不转换了
        char[] array = ptwebqq.toCharArray();

        // 利用栈递归调用
        for (stack.push(new IdxRange(0, array.length - 1)); stack.size() > 0; ) {
            IdxRange range = stack.pop();
            int sIdx = range.s;
            int eIdx = range.e;

            if (sIdx >= eIdx || sIdx < 0 || eIdx >= array.length) {
                continue;
            }

            // 相邻的直接判断是否需要交换
            if (sIdx + 1 == eIdx) {
                // 如果前面值比后面大交换
                if (array[sIdx] > array[eIdx]) {
                    array[sIdx] ^= array[eIdx];
                    array[eIdx] ^= array[sIdx];
                    array[sIdx] ^= array[eIdx];
                }
            } else {
                // 查找
                int oSIdx = sIdx;

                // 记录上次查找的最后索引
                // 使用起始位置值, 作为标准值
                int X = array[sIdx];
                int lEIdx = eIdx;
                for (lEIdx = eIdx, X = array[sIdx]; sIdx < eIdx; ) {
                    // 在区间内从后向前找, 直到找到比标准值小的
                    for (; sIdx < eIdx & array[eIdx] >= X; ) {
                        eIdx--;
                        bytes[0] = (byte) (bytes[0] + 3 & 0xFF);
                        //
                    }

                    // 如果在区间内找到比标准值小的, 则将值放入标准值位置
                    if (sIdx < eIdx) {
                        array[sIdx] = array[eIdx];
                        // 起始索引后移
                        sIdx++;
                        bytes[1] = (byte) (bytes[1] * 13 + 43 & 0xFF);
                    }

                    // 在区间内找比标准值大的
                    for (; sIdx < eIdx && array[sIdx] <= X; ) {
                        sIdx++;
                        //
                        bytes[2] = (byte) (bytes[2] - 3 & 0xFF);
                    }

                    // 如果在区间内找到比标准值大的
                    if (sIdx < eIdx) {
                        // 赋值给刚刚找到的最大值位置
                        array[eIdx] = array[sIdx];
                        // 结束索引前移
                        eIdx--;
                        bytes[3] = (byte) ((bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3] + 1) & 0xFF);
                        //
                    }
                }
                array[sIdx] = (char) X;

                // 压栈递归
                stack.push(new IdxRange(oSIdx, sIdx - 1));
                stack.push(new IdxRange(sIdx + 1, lEIdx));
            }
        }
        return Hex.encode(bytes);
    }

    /**
     * 计算好友列表Hash辅助类
     */
    private static class IdxRange {
        int s;
        int e;

        public IdxRange(int s, int e) {
            this.s = s < 0 ? 0 : s;
            this.e = e < 0 ? 0 : e;
        }
    }


    /**
     * 生成客户端ID
     * 100以内随机数 + 时间低六位
     *
     * @return
     */
    public static long genClientID() {
        return new Random().nextInt(100) * Math.round(1E6) + Math.round(new Date().getTime() % 1E6);
    }
}
